﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Xml;
using System.Xml.Linq;
using ColinHan.BlamBlam.Client.Entities;

namespace ColinHan.BlamBlam.Client.Models
{
    class Serializer
    {
        public static class Constants
        {
            public readonly static string ConfigFolder = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
                "BlaBla");

            public readonly static string FileName = "Data.bla";
            public readonly static string FilePath = Path.Combine(ConfigFolder, FileName);
        }
        public static class XmlConstants
        {
            public const string Namespace = "http://schemas.colinhan.info/blabla";

            public const string Projects = "Projects";
            public const string Project = "Project";
            public const string Task = "Task";
            public const string Record = "Record";
            public const string Break = "Break";

            public const string Subject = "Subject";
            public const string Description = "Description";
            public const string State = "State";
            public const string StartTime = "StartTime";
            public const string EndTime = "EndTime";
        }

        public static void Save(IEnumerable<Project> projects)
        {
            if (Directory.Exists(Constants.ConfigFolder) == false)
            {
                Directory.CreateDirectory(Constants.ConfigFolder);
            }
            if (File.Exists(Constants.FilePath))
            {
                try
                {
                    File.Delete(Constants.FilePath);
                }
                catch (IOException ex)
                {
                    MessageBox.Show(ex.Message, "Save file error");
                    return;
                }
            }

            var writer = XmlWriter.Create(Constants.FilePath);
            writer.WriteStartDocument();
            writer.WriteStartElement(XmlConstants.Projects, XmlConstants.Namespace);

            foreach (var project in projects)
            {
                SaveProject(writer, project);
            }

            writer.WriteEndElement();
            writer.WriteEndDocument();
            writer.Flush();
            writer.Close();
        }
        private static void SaveProject(XmlWriter writer, Project project)
        {
            writer.WriteStartElement(XmlConstants.Project, XmlConstants.Namespace);
            writer.WriteAttributeString(XmlConstants.Subject, XmlConstants.Namespace, project.Subject);
            writer.WriteAttributeString(XmlConstants.Description, XmlConstants.Namespace, project.Description);

            foreach (var task in project.Tasks)
            {
                SaveTask(writer, task);
            }

            writer.WriteEndElement();
        }
        private static void SaveTask(XmlWriter writer, Task task)
        {
            writer.WriteStartElement(XmlConstants.Task, XmlConstants.Namespace);
            writer.WriteAttributeString(XmlConstants.Subject, XmlConstants.Namespace, task.Subject);
            writer.WriteAttributeString(XmlConstants.Description, XmlConstants.Namespace, task.Description);
            writer.WriteAttributeString(XmlConstants.State, XmlConstants.Namespace, task.State.ToString());

            foreach (var record in task.Records)
            {
                SaveRecord(writer, record);
            }

            writer.WriteEndElement();
        }
        private static void SaveRecord(XmlWriter writer, Record record)
        {
            writer.WriteStartElement(XmlConstants.Record, XmlConstants.Namespace);
            if (record.StartTime.HasValue)
            {
                writer.WriteAttributeString(XmlConstants.Subject, XmlConstants.Namespace,
                                            record.StartTime.Value.ToUniversalTime()
                                                .ToString(CultureInfo.InvariantCulture));
            }
            if (record.EndTime.HasValue)
            {
                writer.WriteAttributeString(XmlConstants.Description, XmlConstants.Namespace,
                                            record.EndTime.Value.ToUniversalTime()
                                                .ToString(CultureInfo.InvariantCulture));
            }

            foreach (var @break in record.Breaks)
            {
                SaveBreaks(writer, @break);
            }

            writer.WriteEndElement();
        }
        private static void SaveBreaks(XmlWriter writer, Break @break)
        {
            writer.WriteStartElement(XmlConstants.Break, XmlConstants.Namespace);
            if (@break.StartTime.HasValue)
            {
                writer.WriteAttributeString(XmlConstants.Subject, XmlConstants.Namespace,
                                            @break.StartTime.Value.ToUniversalTime()
                                                .ToString(CultureInfo.InvariantCulture));
            }
            if (@break.EndTime.HasValue)
            {
                writer.WriteAttributeString(XmlConstants.Description, XmlConstants.Namespace,
                                            @break.EndTime.Value.ToUniversalTime()
                                                .ToString(CultureInfo.InvariantCulture));
            }

            writer.WriteEndElement();
        }

        public static IEnumerable<Project> Load()
        {
            if (Directory.Exists(Constants.ConfigFolder) == false)
            {
                return new Project[0];
            }
            if (File.Exists(Constants.FilePath) == false)
            {
                return new Project[0];
            }

            var document = XElement.Load(Constants.FilePath);
            return from p in document.DescendantsAndSelf(XName.Get(XmlConstants.Project, XmlConstants.Namespace))
                   select LoadProject(p);
        }
        private static Project LoadProject(XElement element)
        {
            var p = new Project();
            p.Subject = element.Attribute(XName.Get(XmlConstants.Subject, XmlConstants.Namespace)).Value;
            p.Description = element.Attribute(XName.Get(XmlConstants.Description, XmlConstants.Namespace)).Value;

            p.Tasks.AddRange(
                from t in element.DescendantsAndSelf(XName.Get(XmlConstants.Task, XmlConstants.Namespace))
                select LoadTask(t)
                );
            return p;
        }
        private static Task LoadTask(XElement element)
        {
            var t = new Task();

            t.Subject = element.Attribute(XName.Get(XmlConstants.Subject, XmlConstants.Namespace)).Value;
            t.Description = element.Attribute(XName.Get(XmlConstants.Description, XmlConstants.Namespace)).Value;
            TaskState state;
            Enum.TryParse(element.Attribute(XName.Get(XmlConstants.State, XmlConstants.Namespace)).Value, out state);
            t.State = state;

            t.Records.AddRange(
                from r in element.DescendantsAndSelf(XName.Get(XmlConstants.Record, XmlConstants.Namespace))
                select LoadRecord(t, r)
                );

            return t;
        }
        private static Record LoadRecord(Task task, XElement element)
        {
            var r = new Record(task);

            var startTime = element.Attribute(XName.Get(XmlConstants.StartTime, XmlConstants.Namespace));
            if (startTime != null)
            {
                r.StartTime = DateTime.Parse(startTime.Value).ToLocalTime();
            }
            var endTime = element.Attribute(XName.Get(XmlConstants.EndTime, XmlConstants.Namespace));
            if (endTime != null)
            {
                r.EndTime = DateTime.Parse(endTime.Value).ToLocalTime();
            }

            r.Breaks.AddRange(
                from b in element.DescendantsAndSelf(XName.Get(XmlConstants.Break, XmlConstants.Namespace))
                select LoadBreak(r, b)
                );

            return r;
        }
        private static Break LoadBreak(Record record, XElement element)
        {
            var b = new Break(record);

            var startTime = element.Attribute(XName.Get(XmlConstants.StartTime, XmlConstants.Namespace));
            if (startTime != null)
            {
                b.StartTime = DateTime.Parse(startTime.Value).ToLocalTime();
            }
            var endTime = element.Attribute(XName.Get(XmlConstants.EndTime, XmlConstants.Namespace));
            if (endTime != null)
            {
                b.EndTime = DateTime.Parse(endTime.Value).ToLocalTime();
            }

            return b;
        }
    }
}
